'use strict'

const assert = require('assert');
const EventEmitter3 = require('eventemitter3');
const speedometer = require('speedometer');
const md5File = require('md5-file');
const BitField = require('bitfield');
const fs = require('fs');

const util = require('./util');
const logger = require('./logger')('Torrent');
const Tracker = require('./tracker').Tracker;
const Wire = require('./wire').Wire;
const TorrentShareInfo = require('./torrent-share-info').TorrentShareInfo;
const Piece = require('./torrent-piece').Piece;


const TEMP_FILE_SUFFIX = '.tmp';              // 正在下载中的文件名后缀 例如: xxxxx.mp4.tmp
const RECHOKE_INTERVAL = 10000;               // re choke 时间间隔 10s
const CHOKE_TIMEOUT = 5000;                   // choke

const MAX_CONNECTS = 20;                      // 最多可同时连接的节点数
const MAX_UPLOAD_CONNECTS = 5;                // 最多可同时提供上传的节点数(<=MAX_CONNECTS)
const MAX_BLOCK_LENGTH = 128 * 1024;          // 每次请求资源的最大长度
const HANDSHAKE_TIMEOUT = 25000;              // handshake 超时时间
const RECHOKE_OPTIMISTIC_DURATION = 2;        // 客户端每隔一定的时间重新选择优化非阻塞peer 如30s
const SPEED_THRESHOLD = 3 * Piece.BLOCK_LENGTH; // 下载速度阈值(大于该下载速度时 表示远端节点上传状态非常好)

const PIPELINE_MIN_DURATION = 0.5;
const PIPELINE_MAX_DURATION = 1;

const MAX_RANDOM_PIECE_INDEX_SIZE = 100;


/**
 * Torrent
 *    事件:
 *        'ready': 加载torrenShareInfo -> 加载file(或tmpFile)并且设置了torrentManager后触发
 *        'error': 出现错误时触发,附加参数 'error'
 *        'upload': 向远端节点上传数据时触发, 附加参数 'length'
 *        'download': 从远端节点下载数据时触发, 附加参数 'length'
 *        'piece-selected': 某piece 加入下载队列, 附加参数 'pieceIndex'
 *        'piece-finished': 某piece 下载完成, 附加参数 'pieceIndex'
 *        'done': 下载完成后触发
 *        'pause': 暂停后触发
 *        'resume': 继续后触发
 *        'delete': 删除后触发
 *        'close': 关闭后触发
 */
class Torrent extends EventEmitter3 {
  /**
   * @param {JSON|string} torrentShareInfo
   * @param {*} filePath
   */
  constructor(torrentShareInfo, filePath) {
    super();

    this.fileName = '';
    this.fileHash = '';                      // 文件本身的hash
    this.fileSize = 0;                       // 文件大小
    this.filePieceSize = 0;                  // 每片的大
    this.filePieceCount = 0;                 // 文件可以分成的片数

    this.infoHash = '';
    this._fileFd = null;
    this._filePath = filePath;                // 文件下载完成后保存的路径
    this._tempFilePath = filePath + TEMP_FILE_SUFFIX; // 文件下载未完成前的路径
    this.bitField = null;
    this.pieces = [];
    this._gotPieceCount = 0;

    this._torrentManager = null;
    this._discover = null;

    this._rechokeInterval = null;
    this._rechokeOptimisticWire = null; // 优化非阻塞peer
    this._rechokeOptimisticTimes = RECHOKE_OPTIMISTIC_DURATION;

    this.loadFinished = false; // 加载torrenShareInfo -> 加载file(或tmpFile)成功
    this.isReady = false;
    this.isDone = false;
    this.isPaused = false;
    this.isClosed = false;
    this.inCompleting = false;

    // stats
    this._downloaded = 0;
    this._uploaded = 0;
    this._downloadSpeed = speedometer();
    this._uploadSpeed = speedometer();

    this._wires = [];         // 已建立链接
    this._queue = [];         // 因为超出最大链接数 暂时没有建立链接的节点队列

    this._closeOnErrHappen();

    this._parseTorrentShareInfo(torrentShareInfo);
    this._checkFile();
  }

  get downloadSpeed() {
    return this._downloadSpeed();
  }

  get uploadSpeed() {
    return this._uploadSpeed();
  }

  get download() {
    return this._downloaded;
  }

  get upload() {
    return this._uploaded;
  }

  get gotPieceCount() {
    return this._gotPieceCount;
  }

  _closeOnErrHappen() {
    this.once('error', err => {
      logger.error(`${this.infoHash} fail: ${err.toString()}.`);
      this.close();
    });
  }

  _parseTorrentShareInfo(torrentShareInfo) {
    if (typeof torrentShareInfo === 'string') {
      const torrentFilePath = torrentShareInfo,
        data = fs.readFileSync(torrentFilePath, 'utf8');
      try {
        torrentShareInfo = JSON.parse(data);
      } catch (err) {
        throw new Error('not a torrentShareInfo file!');
      }
    }

    if (!TorrentShareInfo.check(torrentShareInfo)) {
      const errStr = 'torrent info is not corrent';
      logger.error(errStr)
      this.emit('error', new Error(errStr));
    }
    // this.info = torrentShareInfo.info;
    this.fileName = torrentShareInfo.info.name;
    this.fileHash = torrentShareInfo.info.hash;
    this.fileSize = torrentShareInfo.info.size;
    this.filePieceSize = torrentShareInfo.info.pieceSize;
    this.filePieceCount = Math.ceil(this.fileSize / this.filePieceSize);

    this.infoHash = torrentShareInfo.infoHash;
  }

  _checkFile() {
    let isExist = true;
    try {
      fs.accessSync(this._filePath, fs.constants.F_OK | fs.constants.R_OK);
    } catch (error) {
      isExist = false;
    }

    if (isExist) {
      this._loadFile();
    } else {
      this._loadTempFile();
    }
  }

  _loadFile() {
    /**
     * file:
     * |                                            |
     * |                  文件内容                   |
     * |                                            |
     */
    // check file md5
    md5File(this._filePath, (err, hash) => {
      if (this.isClosed) return;
      if (err) {
        this.emit('error', err);
      }
      if (hash !== this.fileHash) {
        this.emit('error', new Error('the file hash is no match ShareInfo!'));
        return;
      }
      fs.open(this._filePath, 'r', (err, fd) => {
        if (this.isClosed) return;

        if (err) {
          this.emit('error', new Error(`open ${this._filePath} fail: ${err.toString()}.`));
          return;
        }

        this._fileFd = fd;
        this.bitField = new BitField(this.filePieceCount);
        for (let i = 0; i < this.filePieceCount; i++) {
          this.bitField.set(i);
          this._gotPieceCount += 1;
        }
        this.loadFinished = true;
        this.emit('loadFinished');
      });
    });
  }

  _loadTempFile() {
    /**
     * tempFile:
     * |                                            |
     * |                  文件内容                   |
     * |                                            |
     * |              bit-fields 内容                |
     */
    const bitFieldsSize = Math.ceil(this.filePieceCount / 8);

    fs.access(this._tempFilePath, fs.constants.F_OK, err => {
      if (this.isClosed) return;

      let flags = 'r+';
      if (err) flags = 'w+';  // file is not exist.

      fs.open(this._tempFilePath, flags, (err, fd) => {
        if (this.isClosed) return;

        if (err) {
          this.emit('error', new Error(`open ${this._tempFilePath} fail: ${err.toString()}.`));
          return;
        }
        this._fileFd = fd;
        fs.fstat(this._fileFd, (err, stats) => {
          if (this.isClosed) return;
          if (err) {
            fs.close(this._fileFd, util.noop);
            this.emit('error', new Error(`stat ${this._tempFilePath} fail: ${err.toString()}.`));
            return;
          }
          if (stats.size !== 0 && (stats.size !== this.fileSize + bitFieldsSize)) {
            fs.close(this._fileFd, util.noop);
            this.emit('error', new Error(`${this._tempFilePath} is not correct Torrent temp file.`));
            return;
          }

          if (stats.size === 0) {
            fs.ftruncate(this._fileFd, this.fileSize + bitFieldsSize, err => {
              if (err) {
                fs.close(this._fileFd, util.noop);
                this.emit('error', new Error(`ftruncate ${this._tempFilePath} fail: ${err.toString()}.`));
                return;
              }
            });
          }

          const buffer = Buffer.allocUnsafe(bitFieldsSize);
          // 读取 bitfield 初始化pieces
          fs.read(this._fileFd, buffer, 0, bitFieldsSize, this.fileSize, err => {
            if (this.isClosed) return;
            if (err) {
              fs.close(this._fileFd, util.noop);
              this.emit('error', new Error(`read ${this._tempFilePath} bit_fields fail: ${err.toString()}.`));
              return;
            }
            // bitField
            this.bitField = new BitField(buffer);
            for (let i = 0; i < this.filePieceCount; i ++) {
              if (this.bitField.get(i)) {
                this._gotPieceCount += 1;
              }
            }

            // pieces
            for (let i = 0; i < this.filePieceCount - 1; i++) {
              this.pieces.push(new Piece(this.filePieceSize));
            }
            this.pieces.push(new Piece(this.fileSize % this.filePieceSize));

            this.emit('loadFinished');
          });
        });
      });
    });
  }

  _checkDone() {
    if (this.isDone) return true;
    for (let i = 0; i < this.filePieceCount; i++) {
      if (!this.bitField.get(i)) return false;
    }
    return true;
  }

  _readFileBlock(index, offset, length, cb) {
    if (!this.isReady) {
      return cb(new Error('not ready or is closed.'), null);
    }

    const position = index * this.filePieceSize + offset
      , buffer = new Buffer(length);

    if (this.inCompleting) {
      this.once('done', () => {
        fs.read(this._fileFd, buffer, 0, length, position, (err, bytesRead, buffer) => {
          cb(err, buffer);
        });
      });
    } else {
      fs.read(this._fileFd, buffer, 0, length, position, (err, bytesRead, buffer) => {
        cb(err, buffer);
      });
    }
  }

  _writeFileBlock(index, offset, length, buffer, cb) {
    if (!this.isReady) {
      return cb(new Error('not ready or is closed.'), null);
    }
    if (this.inCompleting || this.isDone) {
      return cb(null, buffer);
    }

    const position = index * this.filePieceSize + offset;

    fs.write(this._fileFd, buffer, 0, length, position, (err, bytesWritten, buffer) => {
      cb(err, buffer);
    });
  }

  _saveBitFields() {
    if (!this.isReady) return;
    if (this.inCompleting || this.isDone) return;

    fs.write(this._fileFd, this.bitField.buffer, 0, this.bitField.buffer.length, this.fileSize, err => {
      if (err) {
        logger.error(`wirte ${this._tempFilePath} bit fields fail: ${err.toString()}`);
      }
    });
  }

  /**
   * 设置 TorrentManager
   * @param {*} torrentManager
   * @memberof Torrent
   */
  setTorrentManager(torrentManager) {
    if (this._torrentManager) {
      throw new Error('torrentManager is already set!');
    }

    const self = this;

    function start() {
      self.isReady = true;
      self.emit('ready');

      if (self._checkDone()) {
        self.isDone = true;
        self.emit('done');
      }

      self._rechokeInterval = setInterval(() => {
        self._rechoke();
      }, RECHOKE_INTERVAL);
      self._startDiscover();
    }

    this._torrentManager = torrentManager;
    if (this.loadFinished) {
      start();
    } else {
      this.once('loadFinished', () => {
        start();
      });
    }
  }

  /**
   * 添加远端节点
   * @param {*} wire
   * @memberof Torrent
   */
  addWire(wire) {
    if (!this.isReady) {
      throw new Error('not ready!');
    }
    if (wire.isClosed) {
      logger.debug('add wire fail: wire is closed aready, ignore it.');
      return;
    }
    logger.debug('add wire:', wire);

    const peerId = wire.peerId,
      oldWire = this._getWire(peerId);
    if (oldWire) {
      // 每个torrent 同一个远端只保存一个链接
      logger.debug(`${peerId} has aready connected, close it.`);
      wire.close();
    }
    const index = this._queue.indexOf(peerId);
    if (index !== -1) {
      this._queue.splice(index, 1);
    }
    this._addWire(wire);
  }

  _addWire(wire) {
    this._wires.push(wire);
    this._useStrategy(wire);
  }

  /**
   *  为wire实例扩展必要字段
   */
  _appendExt(wire) {
    wire.havePieceCount = 0;
    wire.randomPieceIndexs = [];
    wire.mayBeHaveNeedPiece = true;
  }

  /**
   * 检测远端(wire)是否完成下载
   */
  _checkWireDone(wire, force=false) {
    if (force) {
      let havePieceCount = 0;
      for (let i = 0; i < this.filePieceCount; i++) {
        if (wire.peerBitField.get(i)) havePieceCount++;
      }
      wire.havePieceCount = havePieceCount;
    }
    return wire.havePieceCount === this.filePieceCount;
  }

  /**
  * 随机获取最多(MAX_RANDOM_PIECE_INDEX_SIZE)个 远端节点wire我们需要的 pieceIndex
  * @param {*} wire
  * @param {Array} excludes
  */
  _updateWireRandomPieceIndexs(wire, excludes=[]) {
    if (!wire.randomPieceIndexs.length && !wire.mayBeHaveNeedPiece) return;

    wire.randomPieceIndexs = [];

    let pieceIndexs = []
      , haveNeedPiece = false
      , max = MAX_RANDOM_PIECE_INDEX_SIZE
      , ite = null;

    for (let i = 0; i < this.filePieceCount; i ++) {
      if (!this.bitField.get(i) && wire.peerBitField.get(i)) {
        if (excludes.indexOf(i) === -1) pieceIndexs.push(i);
        haveNeedPiece = true;
      }
    }
    wire.mayBeHaveNeedPiece = haveNeedPiece;
    ite = util.randomIterate(pieceIndexs);
    while(max --) {
      const pieceIndex = ite();
      if (pieceIndex === null) break;
      wire.randomPieceIndexs.push(pieceIndex);
    }
    return;
  }

  _getWire(peerId) {
    for (let i = 0; i < this._wires.length; i ++) {
      const wire = this._wires[i];
      if (wire.peerId === peerId) {
        return wire;
      }
    }
    return null;
  }

  _removeWire(peerId) {
    for (let i = 0; i < this._wires.length; i++) {
      const wire = this._wires[i];
      if (wire.peerId === peerId) {
        delete this._wires.splice(i, 1);
        return;
      }
    }
    return;
  }

  _getEagerPieceIndexs(peerBitField, maxPieces=500) {
    let indexs = []
      , count = 0;
    for (let i = 0; i < this.filePieceCount; i ++) {
      const piece = this.pieces[i];
      if (!piece) continue;
      if (!this.bitField.get(i) && peerBitField.get(i) && !piece.peepNextReseve() !== -1) {
        indexs.push(i);
        count ++;
        if (count >= maxPieces) {
          break;
        }
      }
    }
    if (count === 0) return null;
    return indexs;
  }

  /**
   * 更新节点状态并尝试从节点下载
   */
  _updateAndTryToDownload() {
    if (!this.isReady || this.isDone || this.inCompleting || this.isPaused) return;
    logger.debug('update...');

    const ite = util.randomIterate(this._wires);
    let wire = null;
    while (wire = ite()) {
      this._updateAndTryToDownloadFromWire(wire);
    }
  }

  _updateAndTryToDownloadFromWire(wire) {
    if (!this.isReady || this.isDone || this.inCompleting || this.isPaused) return;
    if (wire.peerChoking) return;

    // 根据以往从节点下载资源的速度 为每个节点设定请求队列大小
    const minOutstandingRequests = this.getWireBlockPipelineLength(wire, PIPELINE_MIN_DURATION)
      , maxOutstandingRequests = this.getWireBlockPipelineLength(wire, PIPELINE_MAX_DURATION)
    if (wire.requests.length >= minOutstandingRequests) return;
    if (!wire.randomPieceIndexs.length) {
      this._updateWireRandomPieceIndexs(wire);
      if (!wire.randomPieceIndexs.length) return;
    }

    // logger.debug(`update wire ${wire.peerId}...`);

    /**
     * 尽可能从下载速度优秀的点下载
     */
    const speedRanker = () => {
      // TODO: 有好的策略?

      // const speed = wire.downloadSpeed() || 1;
      // if (speed > SPEED_THRESHOLD) return () => { return true; };

      // return pieceIndex => {
      //   for (let i = 0; i < this._wires.length; i++) {
      //     if (wire === this._wires[i]) continue;

      //     const otherWire = this._wires[i]
      //       , otherSpeed = otherWire.downloadSpeed()
      //       , otherMinOutstandingRequests = this.getWireBlockPipelineLength(otherWire, PIPELINE_MIN_DURATION);

      //     if (otherSpeed < SPEED_THRESHOLD) continue;
      //     if (!otherWire.bitfield.get(pieceIndex)) continue;
      //     if (otherWire.requests.length > otherMinOutstandingRequests) continue;
      //     if (otherWire.randomPieceIndexs.indexOf(pieceIndex) === -1) continue;

      //     return false;
      //   }
      //   return true;
      // }

      return () => { return true; };
    }

    /**
     * tryNextTick
     */
    const tryNextTick = (wire=null) => {
      process.nextTick(() => {
        if (wire) {
          this._updateAndTryToDownloadFromWire(wire);
        } else {
          this._updateAndTryToDownload();
        }
      });
    };

    /**
     * closeWireNextTick
     */
    const closeWireNextTick = wire => {
      process.nextTick(() => {
        wire.close();
      });
    }

    /**
     * xxxx.yyy.tmp => xxxx.yyy
     */
    const completeFile = () => {
      if (!this.isReady) return;
      if (this.isDone) return;
      if (this.inCompleting) return;

      this.inCompleting = true;

      fs.ftruncate(this._fileFd, this.fileSize, err => {
        if (err) {
          this.emit('error', err);
        }

        fs.close(this._fileFd, err => {
          if (err) {
            this.emit('error', err);
          }

          fs.rename(this._tempFilePath, this._filePath, err => {
            if (err) {
              this.emit('error', err);
            }

            fs.open(this._filePath, 'r', (err, fd) => {
              if (err) {
                this.emit('error', new Error(`open ${this._filePath} fail: ${err.toString()}.`));
              }

              this._fileFd = fd;
              this.pieces = null;
              this.inCompleting = false;
              this.isDone = true;
              this.emit('done');
            });
          })
        })
      });
    }

    /**
     * request
     * @param {*} pieceIndex
     */
    const request = (pieceIndex) => {
      const piece = this.pieces[pieceIndex]
        , reservation = piece.reserve()
        , chunkOffset = piece.chunkOffset(reservation)
        , chunkLength = piece.chunkLength(reservation);

      if (reservation === 0) this.emit('piece-selected', pieceIndex);

      // 向远端节点请求下载数据块
      wire.request(pieceIndex, chunkOffset, chunkLength, (err, chunkBuffer) => {
        if (!this.isReady || this.isDone || this.inCompleting) return;

        // 节点数据已经下载完成/数据块保存出错需重新下载(校验失败/写入文件出错)
        if (this.pieces[pieceIndex] !== piece) {
          tryNextTick();
          return;
        }

        if (err) {
          logger.debug(`error getting piece ${pieceIndex} (offset: ${chunkOffset}  length: ${chunkLength}) from ${wire.peerId}: ${err.toString()}.`);
          piece.cancel(reservation);
          tryNextTick();
          return;
        }

        // logger.debug(`got piece ${pieceIndex} (offset: ${chunkOffset}  length: ${chunkLength}) from ${wire.peerId}.`);
        piece.set(reservation, chunkBuffer);
        if (!piece.finished()) return tryNextTick(wire);

        const buffer = piece.flush();
        this.pieces[pieceIndex] = null;

        tryNextTick(wire);

        // TODO: 也许后期需要检测piece的hash是否正确
        // sha1(buffer ......)

        // 写入文件中
        this._writeFileBlock(pieceIndex, 0, buffer.length, buffer, (err, buffer) => {
          if (err) return this.emit('error', err);

          this.bitField.set(pieceIndex);
          this._gotPieceCount += 1;
          this.emit('piece-finished', pieceIndex);

          if (this._checkDone()) {
            this.once('done', () => {
              for (let i = 0; i < this._wires.length; i++) {
                const wire = this._wires[i];
                logger.debug(`checkDone => ${wire.peerId}: ${this._checkWireDone(wire)}`);
                if (this._checkWireDone(wire)) {
                  closeWireNextTick(wire);
                } else {
                  wire.have(pieceIndex);
                  wire.uninterested();
                }
              }
            });
            completeFile();

          } else {
            for (let i = 0; i < this._wires.length; i++) {
              const wire = this._wires[i];
              wire.have(pieceIndex);
            }
            tryNextTick(wire);
          }

        });
      });
    }

    const ranker = speedRanker()
      , excludes = [];
    while(true) {
      const pieceIndex = wire.randomPieceIndexs[0]
        , piece = this.pieces[pieceIndex];
      if (this.bitField.get(pieceIndex) || !piece || piece.peepNextReseve() === -1) {
        excludes.push(pieceIndex);
        wire.randomPieceIndexs.shift();
        if (!wire.randomPieceIndexs.length) {
          this._updateWireRandomPieceIndexs(wire, excludes);
          if (!wire.randomPieceIndexs.length) {
            if (!wire.requests.length && !wire.mayBeHaveNeedPiece) {
              // TODO: 是否需要向远端发送 uninterested?
              // if (wire.amInterested) wire.uninterested();
            }
            return;
          }
        }
        continue;
      }

      if (ranker(pieceIndex)) {
        // logger.debug(`=> ${pieceIndex}`);

        if (!wire.amInterested) wire.interested();

        while (piece.peepNextReseve() !== -1) {
          if (wire.requests.length >= maxOutstandingRequests) return;
          request(pieceIndex);
        }
      }
      wire.randomPieceIndexs.shift();
    }
  }

  /**
   * 由节点下载速计算节点可承受的负载请求数
   * @param {*} wire
   * @param {*} duration
   */
  getWireBlockPipelineLength(wire, duration) {
    return 2 + Math.ceil(duration * wire.downloadSpeed() / Piece.BLOCK_LENGTH)
  }

  /**
   * 策略
   * @param {*} wire
   */
  _useStrategy(wire) {
    const peerId = wire.peerId;
    let handshakeTimer = null,
      chokeTimer = null;

    this._appendExt(wire);

    /**
     * 启动握手超时监控, 超时后自动关闭
     */
    const startHandShakeTimer = () => {
      handshakeTimer = setTimeout(() => {
        if (!this.isReady) return;
        logger.debug('handshake timeout');
        wire.close();
      }, HANDSHAKE_TIMEOUT);
    };

    /**
     * 关闭握手超时监控
     */
    const stopHandShakeTimer = () => {
      if (!handshakeTimer) return;
      clearTimeout(handshakeTimer);
      handshakeTimer = null;
    };

    /**
     * choke 超时
     */
    const chokeTimeOut = () => {
      if (!wire.isReady) return;
      if (wire.amInterested && this._queue.length > MAX_CONNECTS / 2) {
        wire.close();
      } else {
        chokeTimer = setTimeout(() => {
          chokeTimeOut();
        }, CHOKE_TIMEOUT);
      }
    }

    // ---------------------- Wire Handels  ----------------------
    // ready
    const onWireReady = () => {
      if (!wire.hasSendHandShake) {
        wire.handshake(this.infoHash, this._torrentManager.peerId);
      }
      if (!wire.hasReciveHandShake) {
        startHandShakeTimer();
      } else {
        wire.bitfield(this.bitField);
        wire.uninterested();
      }
    };

    // handshake
    const onHandshake = (infoHash, peerId) => {
      stopHandShakeTimer();
      if (infoHash !== this.infoHash) {
        logger.debug(`handshake except info hash ${this.infoHash}, got ${infoHash}.`);
        wire.close();
      }
      wire.bitfield(this.bitField);
      wire.uninterested();
    };

    // choke
    const onChoke = () => {
      clearTimeout(chokeTimer);
      chokeTimer = setTimeout(() => {
        chokeTimeOut();
      }, CHOKE_TIMEOUT);
    }

    const onUnchoke = () => {
      if (chokeTimer) {
        clearTimeout(chokeTimer);
        chokeTimer = null;
      }
      this._updateAndTryToDownloadFromWire(wire);
    }

    const onInterested = () => {
      wire.unchoke();
    }

    const onBitfield = () => {
      this._checkWireDone(wire, true);
      wire.mayBeHaveNeedPiece = true;
      this._updateAndTryToDownloadFromWire(wire);
    };

    const onHave = (pieceIndex) => {
      wire.havePieceCount += 1;
      if (wire.havePieceCount >= this.filePieceCount) this._checkWireDone(wire, true);
      if (this.bitField.get(pieceIndex)) return;

      if (wire.randomPieceIndexs.length < MAX_RANDOM_PIECE_INDEX_SIZE) {
        wire.randomPieceIndexs.push(pieceIndex);
      } else {
        wire.mayBeHaveNeedPiece = true;
      }
      this._updateAndTryToDownloadFromWire(wire);
    };

    const onRequest = (index, offset, length, cb) => {
      if (length > MAX_BLOCK_LENGTH) {
        logger.debug(`close remote peer:${peerId} that request > 128kb.`);
        return wire.close();
      }
      if (!this.bitField.get(index)) return;
      this._readFileBlock(index, offset, length, cb);
    };

    const onUpload = length => {
      if (!this.isReady) return;

      this._uploadSpeed(length);
      this._uploaded += length;
      this.emit('upload', length);
    }

    const onDownload = length => {
      if (!this.isReady) return;

      this._downloadSpeed(length);
      this._downloaded += length;
      this.emit('download', length);
    }

    const onTimeout = () => {
      logger.debug('timeout');
      wire.close();
    };

    const onError = err => {
      logger.warn(err);
      wire.close();
    };

    const onClose = () => {
      stopHandShakeTimer();
      logger.debug(`${wire.peerId} is closed.`);

      this._removeWire(peerId);
      if (!this.isReady || this.isDone) return;

      if (this._wires.length < MAX_CONNECTS && !this.isPaused && this._queue.length) {
        const peerId = this._queue.shift(),
          peerConn = this._torrentManager.createPeerConn(peerId),
          wire = new Wire(peerConn);
        this._addWire(wire);
      }
      this._updateAndTryToDownload();
    };

    // ------------------------- (end) -------------------------

    if (wire.isReady) onWireReady();
    wire.once('ready', onWireReady);
    wire.once('handshake', onHandshake);
    wire.on('choke', onChoke);
    wire.on('unchoke', onUnchoke);
    wire.on('interested', onInterested);
    if (!this.isDone) {
      wire.on('bitfield', onBitfield);
      wire.on('have', onHave);
    }
    wire.on('request', onRequest);
    wire.on('upload', onUpload);
    wire.on('download', onDownload);
    wire.once('timeout', onTimeout);
    wire.once('error', onError);
    wire.once('close', onClose);
  }

  /**
   *    为了防止有的peer只下载不上传， BitTorrent协议建议， 客户端只给那些向它提供最快下载速度的4个peer上传数据。
   * 简单地说就是谁向我提供下载， 我也提供数据供它下载； 谁不提供数据给我下载， 我的数据也不会上传给它。
   * 客户端每隔一定时间， 比如10秒， 重新计算从各个peer处下载数据的速度， 将下载速度最快的4个peer解除阻塞，
   * 允许这4个peer从客户端下载数据， 同时将其他peer阻塞。
   *
   *     一个例外情况是， 为了发现下载速度更快的peer， 协议还建议， 在任一时刻， 客户端保持一个优化非阻塞peer，
   * 即无论该peer是否提供数据给客户端下载， 客户端都允许该peer从客户端这里下载数据。 由于客户端向peer上传数据，
   * peer接着也允许客户端从peer处下载数据， 并且下载速度超过4个非阻塞peer中的一个。 客户端每隔一定的时间， 如30秒，
   * 重新选择优化非阻塞peer。
   */
  _rechoke () {
    if (!this.isReady) return;

    // logger.debug('rechoke...');

    if (this._rechokeOptimisticTimes > 0) this._rechokeOptimisticTimes -= 1;
    else this._rechokeOptimisticWire = null;

    const peers = [];
    for (let i = 0; i < this._wires.length; i ++) {
      const wire = this._wires[i];
      if (this._checkWireDone(wire) || wire === this._rechokeOptimisticWire) continue;
      peers.push({
        wire: wire,
        downloadSpeed: wire.downloadSpeed(),
        uploadSpeed: wire.uploadSpeed(),
        salt: Math.random(),
        isChoked: true
      });
    }
    peers.sort(rechokeSort);

    let unchokeInterested = 0
      , index = 0;
    for (; index < peers.length && unchokeInterested < MAX_UPLOAD_CONNECTS; ++index) {
      peers[index].isChoked = false
      if (peers[index].wire.peerInterested) unchokeInterested += 1;
    }

    // 重新选择优化非阻塞peer
    if (!this._rechokeOptimisticWire && index < peers.length) {
      const candidates = peers.slice(index).filter((peer) => {
        return peer.wire.peerInterested;
      })
      const optimistic = candidates[util.randomInt(candidates.length)];

      if (optimistic) {
        optimistic.isChoked = false;
        this._rechokeOptimisticWire = optimistic.wire;
        this._rechokeOptimisticTime = RECHOKE_OPTIMISTIC_DURATION;
      }
    }

    peers.forEach(peer => {
      // logger.debug(`${peer.wire.peerId} amChoke: "${peer.wire.amChoking}" -> "${peer.isChoked}"`);
      if (peer.wire.amChoking !== peer.isChoked) {
        if (peer.isChoked) {
          peer.wire.choke();
        }
        else peer.wire.unchoke();
      }
    })

    function rechokeSort(peerA, peerB) {
      // Prefer higher download speed
      if (peerA.downloadSpeed !== peerB.downloadSpeed) {
        return peerB.downloadSpeed - peerA.downloadSpeed
      }

      // Prefer higher upload speed
      if (peerA.uploadSpeed !== peerB.uploadSpeed) {
        return peerB.uploadSpeed - peerA.uploadSpeed
      }

      // Prefer unchoked
      if (peerA.wire.amChoking !== peerB.wire.amChoking) {
        return peerA.wire.amChoking ? 1 : -1
      }

      // Random order
      return peerA.salt - peerB.salt
    }
  }

  /**
   * tracker
   */
  _startDiscover() {
    this._discover = new Tracker(null, this.infoHash, () => {
      // getAonnounceOptions
      return {
        uploaded: this._uploaded,
        downloaded: this._downloaded,
        left: Math.max(this.fileSize - this._downloaded, 0),
      }
    });

    // 发现新节点
    this._discover.on('join', peerId => {
      if (!this.isReady || this.isDone) return;

      if (!this.isPaused && this._wires.length < MAX_CONNECTS) {
        if (this._getWire(peerId)) {
          // 每个torrent 同一个远端只保存一个链接
          return;
        }
        logger.debug(`a new Peer:${peerId} join and we add it as wire for infoHash: ${this.infoHash}.`);
        const peerConn = this._torrentManager.createPeerConn(peerId)
          , wire = new Wire(peerConn);
        this._addWire(wire);
      } else {
        if (this._queue.indexOf(peerId) === -1) {
          this._queue.push(peerId);
        }
      }
    });

    // 节点离线/暂停
    this._discover.on('leave', peerId => {
      const index = this._queue.indexOf(peerId);
      if (index !== -1) {
        this._queue.splice(index, 1);
      }
    });

    this._discover.start();
  }

  /**
   * 获取状态
   */
  getStats() {
    return {
      'downloadSpeed': this._downloadSpeed(),
      'uploadSpeed': this._uploadSpeed(),
      'downloaded': this._downloaded,
      'uploaded': this._uploaded,
    }
  }

  /**
   * 暂停下载
   */
  pause() {
    if (!(this.isReady && !this.isPaused)) {
      return;
    }
    logger.debug('pause');
    this._discover.stop();
    this.isPaused = true;
    this.emit('pause');
  }

  /**
   * 继续下载
   */
  resume() {
    if (!(this.isReady && this.isPaused)) {
      return;
    }
    logger.debug('resume');
    this._discover.start();
    this.isPaused = false;
    this.emit('resume');
    this._updateAndTryToDownload();
  }

  /**
   * 删除文件 可在destroy调用后调用.
   */
  delete() {
    this.close();
    fs.unlink(this._filePath, err => {
      if (err) { return; }
      logger.debug('delete');
      this.emit('delete');
    });
    fs.unlink(this._tempFilePath, err => {
      if (err) { return; }
      logger.debug('delete');
      this.emit('delete');
    });
  }

  /**
   * 关闭
   */
  close() {
    if (this.isClosed) return;
    logger.debug('close');

    if (this.isReady) {
      try {
        this._saveBitFields();
      } catch (err) {
        logger.error(`wirte ${this._tempFilePath} bit fields fail: ${err.toString()}`);
      }
      this.isReady = false;
    }

    this.isClosed = true;

    // stop discover
    if (this._discover) {
      this._discover.stop();
      this._discover = null;
    }

    // clear rechoke intervaler
    if (this._rechokeInterval) {
      clearInterval(this._rechokeInterval);
      this._rechokeInterval = null;
    }

    if (this._wires) {
      for (let wire of this._wires) {
        wire.close();
      }
      this._wires = null;
    }

    // close fd
    if (this._fileFd) {
      fs.close(this._fileFd, () => {});
      this._fileFd = null;
    }
    this.emit('close');
  }
}

module.exports.Torrent = Torrent;
